﻿// 开发者:micropu
// 公司:佛山远钧智慧科技有限公司

using Admin.NET.Application.LogService;
using Furion.Logging.Extensions;
using Furion.Schedule;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using TouchSocket.Core;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace Admin.NET.Application.SysncService.PlatFormSync;


/// <summary>
/// 平台同步定时任务
/// </summary>
[SuppressSniffer]
public class PlatFormSyncJob : IJob
{
    private TcpClient client;
    private IWaitingClient<TcpClient> waitClient;  // 等待型客户端
    private Device device;

    public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
    {
        //Console.WriteLine($"------------------设备子任务{DateTime.Now}");

        var zJLog = App.GetService<ZJLog>();
        var jobDetail = context.JobDetail;
        SqlSugarRepository<Device> _repDevice = new SqlSugarRepository<Device>();
        var qdevice = await _repDevice.AsQueryable().ClearFilter()
            .Includes(u => u.Project)
            .Where(u => u.ClientSn == context.JobId && u.IsDelete == false && u.DeviceConnect == ConnectType.WEBSOCKET)
            .FirstAsync();
        if (qdevice == null)
        {
            //await zJLog.LogPlat(LogLevel.Information, LogType.Error, "数据库不存在该设备", context.JobId, 0, 0, 0, "");
            return;
        }
        device = qdevice;

        try
        {
            // 执行任务 context.JobId = ClientSn
            await StartSyncAsync(stoppingToken, context.JobId, device.ProjectId, Convert.ToInt64(device.TenantId) , Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
        }
        catch (TaskCanceledException)
        {
            await zJLog.LogPlat(LogLevel.Information, LogType.Other, "作业被取消了", context.JobId, device.ProjectId, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
            Dispose();  // 释放资源
        }
        catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException)
        {
            await zJLog.LogPlat(LogLevel.Information, LogType.Other, "作业被取消了", context.JobId, device.ProjectId, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
            Dispose();  // 释放资源
        }
        catch (Exception ex) // 捕获所有其他异常
        {
            await zJLog.LogPlat(LogLevel.Error, LogType.Other, ex.Message+ex.StackTrace, context.JobId, device.ProjectId, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
            Dispose();  // 释放资源
            throw;      // 重新抛出异常
        }
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    /// <exception cref="NotImplementedException"></exception>
    public void Dispose()
    {
        client?.Dispose();
        if (waitClient != null)
        {
            // 确保IWaitingClient<TcpClient>实现IDisposable接口：
            if (waitClient is IDisposable disposableWaitClient)
            {
                disposableWaitClient.Dispose();
            }
        }
    }

    /// <summary>
    /// 开始任务
    /// </summary>
    /// <param name="stoppingToken"></param>
    /// <param name="jobId_ClientSn"></param>
    /// <param name="CreateOrgld"></param>
    /// <param name="CreateOrgName"></param>
    /// <param name="ProjectId"></param>
    /// <param name="Tenantld"></param>
    /// <returns></returns>
    private async Task StartSyncAsync(CancellationToken stoppingToken, string jobId_ClientSn,long ProjectId,long Tenantld,long CreateOrgld,string CreateOrgName)
    {
        await Task.Run(async () =>
        {
            var zJLog = App.GetService<ZJLog>();
            var clientWebSocketManager = App.GetService<IClientWebSocketManager>();
            try
            {

                //是否允许下发人员到设备
                if (device.EnableEmpSend == true) {
                    var OnlineDevicesList = clientWebSocketManager.SelectAllConnections();  // 获取当前在线设备列表
                    if (OnlineDevicesList.Any(d => d.Key == device.ClientSn))
                    {
                        await SyncWhitelistToDeviceAsync();
                    }
                }

                if (device.EnableGetEmpInfo == false && device.EnableUploadLog == false) {

                    //await zJLog.LogPlat(LogLevel.Information, LogType.Error, "该设备不允许拉人及上传", jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                }



                // 创建TCP客户端
                client = new TcpClient();
                client.Setup(new TouchSocketConfig()
                    .SetTcpDataHandlingAdapter(() => new MyFixedHeaderRequestInfo())
                );

                Console.WriteLine($"tcp://{device.Project.ServerIP}:{device.Project.ServerPort}");
                client.Connect($"tcp://{device.Project.ServerIP}:{device.Project.ServerPort}");
                waitClient = client.CreateWaitingClient(new WaitingOptions()
                {
                    FilterFunc = response =>  //设置用于筛选的fun委托，当返回为true时，才会响应返回
                    {
                        return true;
                    }
                });

                if (!waitClient.CanSend)
                {
                    await zJLog.LogPlat(LogLevel.Information, LogType.Error, "连接住建失败", jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                    return;
                }

                // 设备登录
                var IsLogin = false;
                IsLogin = await DeviceLoginAsync();
                if (!IsLogin) {
                    //await zJLog.LogPlat(LogLevel.Information, LogType.Error, "设备登录住建平台失败", jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                    return;
                }
                else{
                    //await zJLog.LogPlat(LogLevel.Information, LogType.Error, "设备成功登录住建平台", jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                }
                await Task.Delay(500);

                // 发送心跳
                var isheart = false;
                if (IsLogin) isheart = await SendHeartbeatAsync();
                if (!isheart)
                {
                    await zJLog.LogPlat(LogLevel.Information, LogType.Error, "设备发送心跳失败", jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                    return;
                }
                await Task.Delay(500);

                // 同步白名单
                if (device.EnableGetEmpInfo)
                {
                    var whiteStr = "";
                    whiteStr = await SyncWhitelistAsync();
                    if (!string.IsNullOrWhiteSpace(whiteStr)) {
                        var Msg = $"{ whiteStr }";
                        //await zJLog.LogPlat(LogLevel.Information, LogType.white, whiteStr, jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                    }
                }
                await Task.Delay(500);
                // 上传考勤记录到住建平台
                if (device.EnableUploadLog)
                {
                    await UploadAttendanceRecordsAsync(); 
                }
                await Task.Delay(500);
            }
            catch (Exception ex)
            {
                // 检查调用栈来判断是否来自DeviceLoginAsync  
                if (ex.StackTrace != null && ex.StackTrace.Contains(nameof(DeviceLoginAsync)))
                {
                    var Msg = $"设备登录异常!";
                    //await zJLog.LogPlat(LogLevel.Information, LogType.Error, Msg, jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                }
                else
                {
                    var Msg = $"设备子任务异常! {ex.Message + ex.StackTrace} !";
                    //await zJLog.LogPlat(LogLevel.Information, LogType.Error, Msg, jobId_ClientSn, ProjectId, Tenantld, CreateOrgld, CreateOrgName);
                }

                Dispose();
            }
            finally
            {
                Dispose();
            }

        }, stoppingToken);
    }


    /// <summary>
    /// 设备登录
    /// </summary>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    private async Task<bool> DeviceLoginAsync()
    {
        var CmdBytes = CreateCmd.CreateCmdLogin(device.Project.ManufacturerId, device.ClientSn);
        MyRequestInfo returnData = (await waitClient.SendThenResponseAsync(CmdBytes)).RequestInfo as MyRequestInfo;
        if (returnData != null && returnData.Flag == 0x0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// 心跳 
    /// </summary>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    private async Task<bool> SendHeartbeatAsync()
    {
        var CmdBytes = CreateCmd.CreateCmdHearbeat();
        MyRequestInfo returnData = (await waitClient.SendThenResponseAsync(CmdBytes)).RequestInfo as MyRequestInfo;
        if (returnData != null && returnData.Flag == 0x0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// 取白名单
    /// </summary>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private async Task<string> SyncWhitelistAsync()
    {
        var CmdBytes = CreateCmd.CreateCmdWhitelist();
        MyRequestInfo returnData = (await waitClient.SendThenResponseAsync(CmdBytes)).RequestInfo as MyRequestInfo;
        if (returnData != null && returnData.Flag == 0x0 && returnData.Command == 849)
        {
            var whitelist = upPacket.upPacketWhiteList(returnData.Content);
            // 同步白名单
            if (whitelist.Records.Count > 0) await DataBaseWhitelistAsync(whitelist);
            await Task.Delay(1000);
            return $"白名单人数{whitelist.Records.Count}人";
        }
        else if (returnData != null && returnData.Flag == 0x1)
        {
            await Task.Delay(1000);
            return $"取白名单失败:{Encoding.UTF8.GetString(returnData.Content)}";
        }
        else
        {
            await Task.Delay(1000);
            return $"取白名单失败时发生异常,跳过...";
        }
    }

    /// <summary>
    /// 同步白名单到数据库
    /// </summary>
    /// <param name="whitelist"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private async Task DataBaseWhitelistAsync(Whitelist whitelist)
    {
        SqlSugarRepository<Employee> _repApp_Employee = new SqlSugarRepository<Employee>();
        SqlSugarRepository<Device> _repApp_Device = new SqlSugarRepository<Device>();
        var zJLog = App.GetService<ZJLog>();

        // 住建ID_List
        var EmpList = await _repApp_Employee.AsQueryable()
            .Where(u => u.ProjectId == device.ProjectId)
            .Select(u => u.ZjEmpID)
            .ToListAsync();

        // 筛选出 WorkerNumber 不在 EmpList 中出现过的数据
        List<DataRecord> dataRecords = whitelist.Records.Where(u => !EmpList.Contains(u.WorkerNumber.ToString()) && u.WorkerNumber > 0).ToList();

        // Except 方法用于计算 EmpList 中不在 Records 中出现过的元素。我们首先使用 Select 方法将 Records 中的 WorkerNumber 投影出来，然后调用 Except 方法来获取差集。
        List<string> DeleteRecord = EmpList.Except(whitelist.Records
            .Where(record => record.WorkerNumber > 0)
            .Select(record => record.WorkerNumber.ToString())).ToList();

        // 是否强制同步人员
        if (device.Project.QiangZhiSync)
        {
            // 删除数据库中不存在的人员
            foreach (var item in DeleteRecord)
            {
               await _repApp_Employee.DeleteAsync(u => u.ZjEmpID == item && u.ProjectId == device.ProjectId);
            }
        }

        // 添加数据库中不存在的人员
        foreach (var item in dataRecords)
        {
            if (!string.IsNullOrEmpty(item.IDNumber) && !string.IsNullOrEmpty(item.WorkerName))
            {
                var CmdBytes = CreateCmd.CreateCmdEmp(device.ClientSn, item.IDNumber);  // 请求人员特征信息 CreateCmdEmp
                MyRequestInfo returnData = (await waitClient.SendThenResponseAsync(CmdBytes)).RequestInfo as MyRequestInfo;
                if (returnData != null && returnData.Flag == 0x0 && returnData.Command == 845)
                {
                    var empInfo = EmpInfo.PacketParser.ParseEmpInfoFromHex(BitConverter.ToString(returnData.Content).Replace("-", ""));
                    // 判断人员是否存在
                    var persons = await _repApp_Employee.AsQueryable()
                        .Select(u => new { ZjEmpID = u.ZjEmpID, ProjectId = u.ProjectId })
                        .Where(u => u.ZjEmpID == item.WorkerNumber.ToString() && u.ProjectId == device.ProjectId)
                        .FirstAsync();
                    if (persons == null)
                    {
                        var Id = Yitter.IdGenerator.YitIdHelper.NextId();
                        var Base64 = Convert.ToBase64String(empInfo.CollectedPhoto);
                        var newEmp = new Employee
                        {
                            Id = Id,
                            ProjectId = device.ProjectId,
                            TenantId = device.TenantId,
                            CreateOrgId = device.CreateOrgId,
                            ZjEmpID = empInfo.WorkerNumber.ToString(),
                            Name = empInfo.Name,
                            Facephoto = Base64,
                            IdNum = empInfo.IDNumber,
                            Nation = empInfo.Ethnicity.ToString(),
                            IsDelete = false,
                            Sex = empInfo.Gender == "1" ? GenderEnum.Male : GenderEnum.Female,
                            Birthday = empInfo.DateOfBirth,
                            Address = empInfo.IDAddress,
                            QianFaJiGuan = empInfo.IssuingAuthority,
                            QiXianShiXiao = empInfo.ExpiryDate,
                        };
                        await _repApp_Employee.InsertAsync(newEmp);
                        var Msg = $"数据库增加人员: {item.WorkerName} 信息成功!";
                        //await zJLog.LogPlat(LogLevel.Information, LogType.GetEmpInfo, Msg, device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                    }
                    Task.Delay(300).Wait();
                }
                else
                {
                    var Msg = $"取 {item.WorkerName} 人员特征信息失败!";
                    //await zJLog.LogPlat(LogLevel.Information, LogType.GetEmpInfo, Msg, device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                }
            }
        }
    }

    /// <summary>
    /// 同步考勤记录
    /// </summary>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private async Task UploadAttendanceRecordsAsync()
    {
        var zJLog = App.GetService<ZJLog>();

        // 上传考勤记录,先取数库中的数据
        SqlSugarRepository<UploadLog> _repApp_UploadLog = new SqlSugarRepository<UploadLog>();
        var uploadLog = await _repApp_UploadLog.AsQueryable()
            .Where(log => log.ClientSn == device.ClientSn && log.IsUpload == false)
            .Where(log => log.AuditingStatus == AuditingStatusType.Yes )
            .ToListAsync();

        foreach (var item in uploadLog)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(item.ImageBase64))
                {
                    var CmdBytes = CreateCmd.CreateCmdUploadEmp(item.EmpSn, item.cap_time, item.ImageBase64);
                    MyRequestInfo returnData = (await waitClient.SendThenResponseAsync(CmdBytes)).RequestInfo as MyRequestInfo;
                    if (returnData != null && returnData.Flag == 0x00 && returnData.Command == 848)
                    {
                        var Msg = $"上传人员: {item.EmpName} 考勤信息成功!";
                        //await zJLog.LogPlat(LogLevel.Information, LogType.UploadLog, Msg, device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                        item.IsUpload = true;
                        item.IsUploadErr = false;
                        item.ErrorMsg = "";
                        await _repApp_UploadLog.Context.Updateable(item).UpdateColumns(it => new { it.IsUpload, it.IsUploadErr, it.ErrorMsg }).ExecuteCommandAsync();
                    }
                    else
                    {
                        var Msg = $"上传人员: {item.EmpName} 考勤信息失败!";
                        await zJLog.LogPlat(LogLevel.Information, LogType.UploadLog, Msg, device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                    }
                    await Task.Delay(500);
                }
            }
            catch (Exception e)
            {
                var Msg = $"Foreach异常 上传 {item.EmpName} 考勤信息失败!{e.Message}";
                await zJLog.LogPlat(LogLevel.Information, LogType.UploadLog, Msg, device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
            }
        }
    }

    /// <summary>
    /// 同步设备人员信息
    /// </summary>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private async Task SyncWhitelistToDeviceAsync()
    {
        string commandId = Yitter.IdGenerator.YitIdHelper.NextId().ToString();
        var zJLog = App.GetService<ZJLog>();
        var message = new
        {
            cmd = "get allpersonid",
            command_id = commandId,
        };
        var webSocketManager = App.GetService<IClientWebSocketManager>();  // 从容器中获取WebSocketManager
        var response =
            await webSocketManager
            .SendAndWaitForResponseAsync(device.ClientSn, JsonConvert.SerializeObject(message), TimeSpan.FromSeconds(15));
        if (response.code == 0) {
            var empIdsList = JsonConvert.DeserializeObject<EmpIdsList>(response.result);

            if (empIdsList.cmd == "get allpersonid" && empIdsList.command_id == commandId && empIdsList.reply == "ACK")
            {
                // ids 是所有人员id。以逗号分割字符串，可能为空字符串表示无人,帮我转为List<string>
                var ids = empIdsList.ids.Split(',').ToList();
                // 查服务端数据库,获取人员该项目的人员列表
                SqlSugarRepository<Employee> _repApp_Employee = new SqlSugarRepository<Employee>();
                var EmpList = await _repApp_Employee.AsQueryable()
                    .Where(u => u.ProjectId == device.ProjectId && u.IsSendClient == false)
                    .Select(u => u.ZjEmpID)
                    .ToListAsync();

                // 筛选出不在数据库 EmpList 中出现过的数据 ,需要删除
                var dataRecords = ids.Where(u => !EmpList.Contains(u)).ToList();
                //await zJLog.LogDevice(LogLevel.Information, LogType.SendEmp, $"设备上多了{dataRecords.Count}人是住建或平台没有的", device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);

                // Except 方法用于计算 EmpList 中不在 Ids 中出现过的元素 , 需增加
                List<string> AddRecord = EmpList.Except(ids.Select(record => record)).ToList();
                //await zJLog.LogDevice(LogLevel.Information, LogType.SendEmp, $"设备上少了{AddRecord.Count}人需要下发", device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);


                // 删除设备上的人员
                string DeleteCommandId = Yitter.IdGenerator.YitIdHelper.NextId().ToString();
                // List<string> dataRecords 以逗号分割成字符串
                string DeleteIds = string.Join(",", dataRecords);
                if (!string.IsNullOrEmpty(DeleteIds))
                {
                    var DeleteMessage = new
                    {
                        cmd = "delete person(s)",
                        command_id = DeleteCommandId,
                        flag = -1,
                        id = DeleteIds,
                    };
                    var DeleteResponse =
                        await webSocketManager
                        .SendAndWaitForResponseAsync(device.ClientSn, JsonConvert.SerializeObject(DeleteMessage), TimeSpan.FromSeconds(15));
                    if (DeleteResponse.code == 0)
                    {
                        await zJLog.LogDevice(LogLevel.Information, LogType.SendEmp, $"设备删除以下人员{DeleteIds}成功", device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                    }
                    else
                    {
                        //await zJLog.LogDevice(LogLevel.Information, LogType.SendEmp, $"设备删除人员失败,{DeleteResponse.msg}", device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                    }
                }

                // 添加设备上的人员
                foreach (var record in AddRecord)
                {
                    // 在数据库上查出这个人员。然后添加到设备上
                    var EmpInfo = await _repApp_Employee.AsQueryable()
                        .Where(u => u.ProjectId == device.ProjectId && u.ZjEmpID == record)
                        .SingleAsync();

                    string AddCommandId = Yitter.IdGenerator.YitIdHelper.NextId().ToString();
                    var AddMessage = new
                    {
                        cmd = "upload person",
                        command_id = AddCommandId,
                        id = record,
                        name = EmpInfo.Name,
                        role = 1,
                        reg_image = EmpInfo.Facephoto,
                        customer_text = string.IsNullOrWhiteSpace(EmpInfo.IdNum) ? "123" : EmpInfo.IdNum,
                    };
                    var AddResponse =
                        await webSocketManager
                        .SendAndWaitForResponseAsync(device.ClientSn, JsonConvert.SerializeObject(AddMessage), TimeSpan.FromSeconds(15));
                    if (AddResponse.code != 0)
                    {
                        //因下发出错,更新数据库，不让再添加了
                        EmpInfo.IsSendClient = true;
                        EmpInfo.SendClientErr = AddResponse.code.GetDescription();
                        await _repApp_Employee.UpdateAsync(EmpInfo);
                    }
                    else
                    {
                        await zJLog.LogDevice(LogLevel.Information, LogType.SendEmp, $"工地设备上添加人员:{EmpInfo.Name}信息成功", device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
                    }
                    await Task.Delay(300);
                }
            }
            await Task.Delay(3000);
        }
        else
        {
            await zJLog.LogDevice(LogLevel.Information, LogType.UploadLog, $"设备同步人员失败:{response.msg}", device.ClientSn, device.Project.Id, Convert.ToInt64(device.TenantId), Convert.ToInt64(device.CreateOrgId), device.CreateOrgName);
        }
    }

}

/// <summary>
/// 人员IDS列表
/// </summary>
public class EmpIdsList
{
    public string cmd { get; set; }
    public ResponseCode code { get; set; }
    public string command_id { get; set; }
    public string device_sn { get; set; }
    public string ids { get; set; }
    public string reply { get; set; }
}